using System;
using System.IO;
using System.Threading;
using System.Runtime.Remoting;
using System.Collections.Generic;
using System.Text;
using Weazel.Gezel.Model;
using Weazel.Gezel.Simulator.Reference;

using Weazel.Microblaze.Elf;
using Weazel.Microblaze.Simulator.Components;

using Weazel.Microblaze.Simulator.Components.Opb;
using Weazel.Microblaze.Simulator.Components.Ccd;
using Weazel.Microblaze.Simulator.Components.Lcd;
using Weazel.Microblaze.Simulator.Components.Gezel;
using Weazel.Microblaze.Simulator.Components.GenericRam;
using Weazel.Microblaze.Simulator.Components.OpbUartLite;
using Weazel.Microblaze.Simulator.Components.Microblaze;

namespace Weazel.Microblaze.Simulator.Engine
{
    public class SimulationContext
    {
        private List<Component> components = new List<Component>();
        public List<Component> Components
        {
            get { return components; }            
        }

        private MicroblazeComponent microblaze;
        private OpbGezelComponent gezelComponent;
        private Opb opb;
        private Lcd lcd;
        private Ccd ccd;
        private GenericRam rom;
        private GenericRam ram;
        private OpbUartLite uart;

        public event EventHandler SimulationStateChanged;
        public event EventHandler<ProgressEventArgs> ProgressEvent;

        internal void RaiseProgressEvent(string text)
        {
            if (ProgressEvent != null)
                ProgressEvent(this, new ProgressEventArgs(text));
        }

        /// <summary>
        /// Get the number of cycles executed in this simulation
        /// instance.
        /// </summary>
        private ulong cyclesExecuted;        
        public ulong CyclesExecuted
        {
            get { return cyclesExecuted; }
        }

        private Configuration configuration;
        public Configuration Configuration
        {
            get { return configuration; }            
        }
        
        private List<string> romSections = new List<string>();
        private List<string> ramSections = new List<string>();

        private TextWriter opbIpifOutputWriter = Console.Out;
        public TextWriter OpbIpifOutputWriter
        {
            get { return opbIpifOutputWriter; }
            set { opbIpifOutputWriter = value; }
        }

        private TextWriter consoleOutputWriter = Console.Out;
        public TextWriter ConsoleOutputWriter
        {
            get { return consoleOutputWriter; }
            set { consoleOutputWriter = value; }
        }

        private TextWriter outputWriter = Console.Out;
        public TextWriter OutputWriter
        {
            get { return outputWriter; }
            set { outputWriter = value; }
        }

        public SimulationContext(Configuration configuration)
        {
            this.configuration = configuration;
        }

        public void CleanUp()
        {
            foreach (Component component in Components)
                component.CleanUp();            
        }



        private void setupPlatform()
        {
            this.microblaze = new Components.Microblaze.MicroblazeComponent(this);
            this.opb = new Components.Opb.Opb(this);
            this.lcd = new Components.Lcd.Lcd(this, 0x40400000, 0x40401fff);
            this.ccd = new Components.Ccd.Ccd(this, 0x40500000, 0x40501fff);
            this.uart = new Components.OpbUartLite.OpbUartLite(this, 0x40600000, 0x40601fff);

            this.rom = new Components.GenericRam.GenericRam(this, 0, 0x00100000, true);
            this.ram = new Components.GenericRam.GenericRam(this, 0x2000000, 0x2100000, false);

            this.opb.RegisterMaster(this.microblaze);
            this.opb.RegisterSlave(this.lcd);
            this.opb.RegisterSlave(this.ccd);
            this.opb.RegisterSlave(this.uart);
            this.opb.RegisterSlave(this.ram);

            this.opb.RegisterSlave(this.rom);


             microblaze.Opb = this.opb;
             this.microblaze.InstructionMemorySpace = this.rom;
             this.microblaze.DataMemorySpace = this.opb;



            this.components.AddRange(
                new Component[] { 
                    this.microblaze,
                    this.opb,
                    this.lcd,
                    this.ccd, 
                    this.uart,
                    this.rom,
                    this.ram
                }
            );


            if (configuration.GezelModel != null)
            {
                this.gezelComponent = new OpbGezelComponent(this, opb, configuration.GezelModel);
                this.components.Add(this.gezelComponent);
            }
        }

        public bool Initialize()
        {
            RaiseProgressEvent("Configuring platform (memory space)");

            // setup the memory space
            setupPlatform();

            RaiseProgressEvent("Loading Elf file section");

            // setup default sections to load. 
            romSections.Add(".vectors.reset");
            romSections.Add(".vectors.sw_exception");
            romSections.Add(".vectors.interrupt");
            romSections.Add(".vectors.hw_exception");
            
            ramSections.Add(".ctors");
            ramSections.Add(".dtors");
            ramSections.Add(".bss");
            ramSections.Add(".data");
            ramSections.Add(".sdata");
            ramSections.Add(".heap");
            ramSections.Add(".stack");

            romSections.Add(".text");
            romSections.Add(".init");
            romSections.Add(".fini");
            romSections.Add(".rodata");
            romSections.Add(".jcr");
            
            // load ELF file data into memory
            foreach (ElfSectionHeader header in configuration.ElfFile.SectionHeaders)
            {
                if (header.Name == ".text")
                {
                    /*
                    Console.WriteLine("Disassembling .text section");
                    Disassembler disassembler = new Disassembler();
                    disassembler.DisassembleToDisk(header.Sh_addr, header.Content, "text.dis");
                     * */
                }

                foreach (string ramSectionToLoad in ramSections)
                {
                    if (ramSectionToLoad == header.Name)
                    {
                        try
                        {
                            ram.Load(header.Content, header.Sh_addr);                            
                            Console.WriteLine("Loading section '{0}' at RAM address 0x{1:X}", header.Name, header.Sh_addr);
                        }
                        catch (MemoryException)
                        {
                            string message =
                              string.Format("Error loading section '{0}' to address 0x{1:X}", header.Name, header.Sh_addr);

                            System.Windows.Forms.MessageBox.Show(message);

                            return false;
                        }
                    }
                }

                foreach (string romSectionToLoad in romSections)
                {
                    if (romSectionToLoad == header.Name)
                    {
                        try
                        {
                            rom.Load(header.Content, header.Sh_addr);
                            Console.WriteLine("Loading section '{0}' at ROM address 0x{1:X}", header.Name, header.Sh_addr);
                        }
                        catch (MemoryException)
                        {
                            string message =
                              string.Format("Error loading section '{0}' to address 0x{1:X}", header.Name, header.Sh_addr);

                            System.Windows.Forms.MessageBox.Show(message);

                            return false;
                        }
                    }
                }
            }

            return true;
        }

        public static Queue<Transaction> TransactionUpdateQueue = new Queue<Transaction>();

        private WaitHandle[] GetTickFinishedHandles()
        {
            List<WaitHandle> tickFinishedHandles = new List<WaitHandle>();
            foreach (Component component in components)
                tickFinishedHandles.Add(component.TickFinishedEvent);
            return tickFinishedHandles.ToArray();
        }

        private WaitHandle[] GetVariabledUpdatedHandles()
        {
            List<WaitHandle> variablesUpdatedHandles = new List<WaitHandle>();
            foreach (Component component in components)
                variablesUpdatedHandles.Add(component.VariablesUpdatedEvent);
            return variablesUpdatedHandles.ToArray();
        }

        public void Step()
        {
            WaitHandle[] tickFinishedHandles = GetTickFinishedHandles();
            WaitHandle[] variablesUpdatedHandles = GetVariabledUpdatedHandles();

            foreach (Component component in components)
                component.Tick();            

            WaitHandle.WaitAll(tickFinishedHandles);

            foreach (Component component in components)
                component.VariableUpdateEvent.Set();

            while (TransactionUpdateQueue.Count > 0)
                TransactionUpdateQueue.Dequeue().UpdateStatus();

            WaitHandle.WaitAll(variablesUpdatedHandles);

            cyclesExecuted++;            
        }
    }
}
